#include "SocketOpt.h"
#include <array>
#include <cstddef>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <string>
#include <sys/uio.h>  // readv
#include <unistd.h>
#include "BytesEndian.h"
#include "PlatLogger.h"
#include "Util.h"
namespace xncc {
namespace foundation {
    void SocketOpt::setNonBlockAndCloseOnExec(int sockfd)
    {
        // non-block
        int flags = ::fcntl(sockfd, F_GETFL, 0);
        flags |= O_NONBLOCK;
        int ret = ::fcntl(sockfd, F_SETFL, flags);
        // close-on-exec
        flags = ::fcntl(sockfd, F_GETFD, 0);
        flags |= FD_CLOEXEC;
        ret = ::fcntl(sockfd, F_SETFD, flags);
        (void)ret;
    }

    const struct sockaddr* SocketOpt::sockaddr_cast(const struct sockaddr_in6* addr)
    {
        return static_cast<const struct sockaddr*>(static_cast<const void*>(addr));
    }

    struct sockaddr* SocketOpt::sockaddr_cast(struct sockaddr_in6* addr)
    {
        return static_cast<struct sockaddr*>(static_cast<void*>(addr));
    }

    struct sockaddr* SocketOpt::sockaddr_cast(struct sockaddr_in* addr)
    {
        return static_cast<struct sockaddr*>(static_cast<void*>(addr));
    }

    const struct sockaddr* SocketOpt::sockaddr_cast(const struct sockaddr_in* addr)
    {
        return static_cast<const struct sockaddr*>(static_cast<const void*>(addr));
    }

    const struct sockaddr* SocketOpt::sockaddr_cast(const struct sockaddr_un* addr)
    {
        return static_cast<const struct sockaddr*>(static_cast<const void*>(addr));
    }

    struct sockaddr* SocketOpt::sockaddr_cast(struct sockaddr_un* addr)
    {
        return static_cast<struct sockaddr*>(static_cast<void*>(addr));
    }

    const struct sockaddr_in* SocketOpt::sockaddr_in_cast(const struct sockaddr* addr)
    {
        return static_cast<const struct sockaddr_in*>(static_cast<const void*>(addr));
    }

    const struct sockaddr_in6* SocketOpt::sockaddr_in6_cast(const struct sockaddr* addr)
    {
        return static_cast<const struct sockaddr_in6*>(static_cast<const void*>(addr));
    }

    const struct sockaddr_un* SocketOpt::sockaddr_un_cast(const struct sockaddr* addr)
    {
        return static_cast<const struct sockaddr_un*>(static_cast<const void*>(addr));
    }

    int SocketOpt::createOrDie(sa_family_t family, int protocol, int type)
    {
        int sockfd = ::socket(family, type, protocol);
        if (sockfd < 0) {
            throw shared::system_exception("create sockfd failed");
        }
        return sockfd;
    }

    int SocketOpt::createNonblockingOrDie(sa_family_t family, int protocol, int type)
    {
        int sockfd = createOrDie(family, protocol, type);
        setNonBlockAndCloseOnExec(sockfd);
        return sockfd;
    }

    ssize_t SocketOpt::read(int sockfd, void* buf, size_t count) { return ::read(sockfd, buf, count); }

    ssize_t SocketOpt::readv(int sockfd, const struct iovec* iov, int iovcnt) { return ::readv(sockfd, iov, iovcnt); }

    ssize_t SocketOpt::write(int sockfd, const void* buf, size_t count) { return ::write(sockfd, buf, count); }

    void SocketOpt::close(int sockfd)
    {
        if (::close(sockfd) < 0) {
            SYSTEM_ERROR << "sockets::close"_s;
        }
    }

    void SocketOpt::shutdownWrite(int sockfd)
    {
        if (::shutdown(sockfd, SHUT_WR) < 0) {
            SYSTEM_ERROR << "sockets::shutdownWrite"_s;
        }
    }

    std::string SocketOpt::toIpPort(const struct sockaddr* addr)
    {
        const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
        uint16_t                  port  = networkToHost16(addr4->sin_port);
        return toIp(addr) + ":" + std::to_string(port);
    }

    std::string SocketOpt::toIp(const struct sockaddr* addr)
    {
        constexpr int                 MAX_IP_SIZE = 64;
        std::array<char, MAX_IP_SIZE> buf         = {};
        if (addr->sa_family == AF_INET) {
            assert(buf.size() >= INET_ADDRSTRLEN);
            const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
            ::inet_ntop(AF_INET, &addr4->sin_addr, buf.data(), static_cast<socklen_t>(buf.size()));
        }
        else if (addr->sa_family == AF_INET6) {
            assert(buf.size() >= INET6_ADDRSTRLEN);
            const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
            ::inet_ntop(AF_INET6, &addr6->sin6_addr, buf.data(), static_cast<socklen_t>(buf.size()));
        }
        return {buf.data()};
    }

    std::string SocketOpt::toStr(const struct sockaddr* addr)
    {
        if (addr->sa_family == AF_INET || addr->sa_family == AF_INET6) {
            return toIpPort(addr);
        }
        if (addr->sa_family == AF_UNIX) {
            const struct sockaddr_un* addrUds = sockaddr_un_cast(addr);
            return addrUds->sun_path;
        }
        return "";
    }

    void SocketOpt::setReuseAddr(bool on, int sockfd)
    {
        int optval = on ? 1 : 0;
        int ret    = ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, static_cast<socklen_t>(sizeof optval));
        if (ret < 0 && on) {
            SYSTEM_ERROR << "SO_REUSEPORT failed."_s;
        }
    }

    void SocketOpt::setReusePort(bool on, int sockfd)
    {
#ifdef SO_REUSEPORT
        int optval = on ? 1 : 0;
        int ret    = ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, static_cast<socklen_t>(sizeof optval));
        if (ret < 0 && on) {
            SYSTEM_ERROR << "SO_REUSEPORT failed."_s;
        }
#else
        if (on) {
            SYSTEM_ERROR << ("SO_REUSEPORT is not supported."_s);
        }
#endif
    }

    void SocketOpt::setTcpKeepAlive(bool on, int sockfd)
    {
#ifdef SO_KEEPALIVE
        int optval = on ? 1 : 0;
        int ret    = ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, static_cast<socklen_t>(sizeof optval));
        if (ret < 0 && on) {
            SYSTEM_ERROR << "SO_KEEPALIVE failed."_s;
        }
        int idle     = 60;  // 设置idle时间为60秒
        int interval = 10;  // 设置interval时间为10秒
        int probes   = 3;   // 设置probes次数为3次

        setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
        setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
        setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &probes, sizeof(probes));
#else
        if (on) {
            SYSTEM_ERROR << ("SO_KEEPALIVE is not supported."_s);
        }
#endif
        {
        }
    }

    int SocketOpt::getSocketError(int sockfd)
    {
        int  optval = 0;
        auto optlen = static_cast<socklen_t>(sizeof optval);

        if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
            return errno;
        }
        return optval;
    }

    struct sockaddr_un SocketOpt::getLocalAddr(int sockfd)
    {
        struct sockaddr_un localaddr = {};
        shared::memZero(localaddr);
        auto addrlen = static_cast<socklen_t>(sizeof localaddr);
        if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0) {
            SYSTEM_ERROR << "getsockname"_s;
        }
        return localaddr;
    }

    struct sockaddr_un SocketOpt::getPeerAddr(int sockfd)
    {
        struct sockaddr_un peeraddr = {};
        shared::memZero(&peeraddr);
        auto addrlen = static_cast<socklen_t>(sizeof peeraddr);
        if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0) {
            SYSTEM_ERROR << "getpeername"_s;
        }
        return peeraddr;
    }

    std::string SocketOpt::getLocalAddrStr(int sockfd)
    {
        struct sockaddr_un sockaddr = getLocalAddr(sockfd);
        return toStr(sockaddr_cast(&sockaddr));
    }
    std::string SocketOpt::getPeerAddrStr(int sockfd)
    {
        struct sockaddr_un sockaddr = getPeerAddr(sockfd);
        return toStr(sockaddr_cast(&sockaddr));
    }

    bool SocketOpt::isSelfConnect(int sockfd)
    {
        struct sockaddr_un localaddr = getLocalAddr(sockfd);
        struct sockaddr_un peeraddr  = getPeerAddr(sockfd);
        struct sockaddr*   addr      = sockaddr_cast(&localaddr);
        if (addr->sa_family == AF_INET) {
            const auto* laddr4 = reinterpret_cast<struct sockaddr_in*>(&localaddr);
            const auto* raddr4 = reinterpret_cast<struct sockaddr_in*>(&peeraddr);
            return laddr4->sin_port == raddr4->sin_port && laddr4->sin_addr.s_addr == raddr4->sin_addr.s_addr;
        }
        if (addr->sa_family == AF_INET6) {
            const auto* laddr6 = reinterpret_cast<struct sockaddr_in6*>(&localaddr);
            const auto* raddr6 = reinterpret_cast<struct sockaddr_in6*>(&peeraddr);
            return laddr6->sin6_port == raddr6->sin6_port &&
                   memcmp(&laddr6->sin6_addr, &raddr6->sin6_addr, sizeof laddr6->sin6_addr) == 0;
        }
        return false;
    }
}  // namespace foundation
}  // namespace xncc